package common.config.exception;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import common.config.http.HttpStatusEnum;
import common.model.response.base.Result;
import common.util.HttpRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.RestClientException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * 全局异常处理
 *
 * @author 米泽鹏
 * @since 2021-07-09 下午 4:35
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

	/**
	 * 自定义异常
	 */
	@ExceptionHandler(CustomException.class)
	public Result<?> customExceptionHandler(CustomException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		Integer code = e.getCode() != null ? e.getCode() : Optional.ofNullable(HttpStatusEnum.getStatus(HttpRequestUtil.getRequest())).orElse(HttpStatusEnum.INTERNAL_SERVER_ERROR).getCode();
		String errMsg = ExceptionUtil.getSimpleMessage(e);
		String message = StrUtil.isNotBlank(errMsg) ? errMsg : HttpStatusEnum.getMessageByCode(code);
		return Result.error(code, message);
	}

	/**
	 * 空指针异常
	 */
	@ExceptionHandler(NullPointerException.class)
	public Result<?> nullPointerExceptionHandler(NullPointerException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		return Result.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), HttpStatusEnum.ExceptionMessage.NULL_POINTER_EXCEPTION);
	}

	/**
	 * 404错误：NoHandlerFoundException
	 */
	@ExceptionHandler(NoHandlerFoundException.class)
	public Result<?> noHandlerFoundExceptionHandler(NoHandlerFoundException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		HttpServletRequest request = HttpRequestUtil.getRequest();
		if (request == null) {
			return Result.error(HttpStatusEnum.NOT_FOUND.getCode(), HttpStatusEnum.NOT_FOUND.getMessage());
		} else {
			return Result.error(HttpStatusEnum.NOT_FOUND.getCode(), HttpStatusEnum.NOT_FOUND.getMessage() + "：" + request.getRequestURI());
		}
	}

	/**
	 * 类型转换异常
	 */
	@ExceptionHandler(ClassCastException.class)
	public Result<?> classCastExceptionHandler(ClassCastException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		return Result.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), HttpStatusEnum.ExceptionMessage.CLASS_CAST_EXCEPTION);
	}

	/**
	 * 数组下标越界异常
	 */
	@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
	public Result<?> arrayIndexOutOfBoundsExceptionHandler(ArrayIndexOutOfBoundsException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		return Result.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), HttpStatusEnum.ExceptionMessage.ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION);
	}

	/**
	 * 下标越界异常
	 */
	@ExceptionHandler(IndexOutOfBoundsException.class)
	public Result<?> indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		return Result.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), HttpStatusEnum.ExceptionMessage.INDEX_OUT_OF_BOUNDS_EXCEPTION);
	}

	/**
	 * 参数校验异常：MethodArgumentNotValidException
	 */
	@ExceptionHandler(MethodArgumentNotValidException.class)
	public Result<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		int statusCode = Optional.ofNullable(HttpStatusEnum.getStatus(HttpRequestUtil.getRequest())).orElse(HttpStatusEnum.INTERNAL_SERVER_ERROR).getCode();
		return Result.error(statusCode, handleMethodArgumentNotValidExceptionMessage(e));
	}

	/**
	 * 参数校验异常：BindException
	 */
	@ExceptionHandler(BindException.class)
	public Result<?> bindExceptionHandler(BindException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		int statusCode = Optional.ofNullable(HttpStatusEnum.getStatus(HttpRequestUtil.getRequest())).orElse(HttpStatusEnum.INTERNAL_SERVER_ERROR).getCode();
		return Result.error(statusCode, handleBindExceptionMessage(e));
	}

	/**
	 * 参数校验异常：ConstraintViolationException
	 */
	@ExceptionHandler(ConstraintViolationException.class)
	public Result<?> constraintViolationExceptionHandler(ConstraintViolationException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		return Result.error(HttpStatusEnum.INTERNAL_SERVER_ERROR.getCode(), handleConstraintViolationExceptionMessage(e));
	}

	@ExceptionHandler(RestClientException.class)
	public Result<?> restClientExceptionHandler(RestClientException e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		int statusCode = Optional.ofNullable(HttpStatusEnum.getStatus(HttpRequestUtil.getRequest())).orElse(HttpStatusEnum.INTERNAL_SERVER_ERROR).getCode();
		return Result.error(statusCode, ExceptionUtil.getSimpleMessage(e));
	}

	@ExceptionHandler(Exception.class)
	public Result<?> exceptionHandler(Exception e) {
		log.error(ExceptionUtil.getRootCauseMessage(e), e);
		int statusCode = Optional.ofNullable(HttpStatusEnum.getStatus(HttpRequestUtil.getRequest())).orElse(HttpStatusEnum.INTERNAL_SERVER_ERROR).getCode();
		return Result.error(statusCode, ExceptionUtil.getSimpleMessage(e));
	}

	/**
	 * 处理MethodArgumentNotValidException错误信息
	 */
	private String handleMethodArgumentNotValidExceptionMessage(MethodArgumentNotValidException e) {
		List<ObjectError> errors = e.getBindingResult().getAllErrors();
		StringBuilder stringBuilder = new StringBuilder();
		for (ObjectError error : errors) {
			if (stringBuilder.length() != 0) {
				stringBuilder.append("，");
			}
			stringBuilder.append(error.getDefaultMessage());
		}
		return stringBuilder.toString();
	}

	/**
	 * 处理BindException错误信息
	 */
	private String handleBindExceptionMessage(BindException e) {
		List<ObjectError> errors = e.getAllErrors();
		StringBuilder stringBuilder = new StringBuilder();
		for (ObjectError error : errors) {
			if (stringBuilder.length() != 0) {
				stringBuilder.append(",");
			}
			stringBuilder.append(error.getDefaultMessage());
		}
		return stringBuilder.toString();
	}

	/**
	 * 处理ConstraintViolationException错误信息
	 */
	private String handleConstraintViolationExceptionMessage(ConstraintViolationException e) {
		Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
		StringBuilder stringBuilder = new StringBuilder();
		for (ConstraintViolation<?> violation : violations) {
			if (stringBuilder.length() != 0) {
				stringBuilder.append("，");
			}
			stringBuilder.append(violation.getMessage());
		}
		return stringBuilder.toString();
	}

}
